using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;

namespace ADDDF.Infrastructure
{
    /// <summary>
    /// EntityBase is the base class for which all entity objects should derive from.
    /// 
    /// Entity key cannot be null throughout the entity's life cycle.
    /// 
    /// The classic DDD book urges that 1) an entity must be identified by a key, and
    /// 2) the life of an entity starts when it has been created by a factory method. 
    /// This requires that an entity must objain a key before it is added into the 
    /// repository. However, in real world many databases uses database generated 
    /// primary keys (such as SQL Server's identity column). This makes the above 
    /// requirement impossible to implement. Many domain driven frameworks thus uses
    /// Guid as the entity key as well as the primary keys for their databases (such 
    /// as the one in Tim McCarthy's book: .NET Domain-Driven Design with C#: Problem 
    /// - Design - Solution).
    /// 
    /// This framework attempts to support the scenario where entity keys sometimes 
    /// needs to be assigned by the repository when it is being added. 
    /// 
    /// There are two ways an entity gets its key:
    /// Type-1. An entity can get its key upon creation. Either with an entity factory or
    ///   simply through the constructor. This key does not change over the life-time
    ///   of the entity.
    /// Type-2. An entity can get its key upon adding into the repository. The repository
    ///   probably gets a database generated key upon insertion. Upon creation, this
    ///   type of entity's key is undefined (null).
    /// 
    /// The default constructor of EntityBase creates an entity with null key. 
    /// Another constructor which takes a key parameter expect a non-null value.
    /// A Type-1 entity only need to have the constructor that takes a non-null value.
    /// A Type-2 entity should provide ways to do both.
    /// 
    /// An entity with a null key cannot be compared (by key) to another entity.
    /// </summary>
    public abstract class EntityBase : IEntity
    {
        #region Fields

        private object _key;

        #endregion

        #region Construcotrs

        /// <summary>
        /// Create an entity with a key. 
        /// </summary>
        /// <param name="key">Key of an entity. Cannot be null.</param>
        protected EntityBase(object key)
        {
            if (key == null)
                throw new ArgumentNullException("The key of an entity cannot be null");

            _key = key;
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets Key. 
        /// </summary>
        public object Key 
        {
            get { return _key; }            
        }

        #endregion

        #region Key Access Methods

        /// <summary>
        /// Sets the key of entity. Derviced classes can determine the best convention for 
        /// managing entity's key.
        /// </summary>
        /// <param name="key">New key for the entity. Cannot be null.</param>
        protected void SetKey(object key)
        {
            if (key == null)
                throw new ArgumentNullException("The key of an entity cannot be null");

            _key = key;
        }

        #endregion

        #region Equality Tests

        /// <summary>
        /// Check whether both entities have equivalent keys. 
        /// </summary>
        /// <param name="another">An entity to compare with</param>
        /// <returns></returns>
        public bool SameKeyAs(IEntity another)
        {
            return EntityHelper.HavingSameKeys(this, another);
        }

        /// <summary>
        /// Check whether two entities are consider to be the same instance. The 
        /// default implementation compares their keys, and requires that their
        /// types must also be equivalent.
        /// </summary>
        /// <param name="entity"></param>
        /// <returns></returns>
        public bool SameEntityAs(IEntity another)
        {
            return EntityHelper.AreSame(this, another);
        }
                
        /// <summary>
        /// Check whether both entities have the same value, a "deep" comparison.
        /// The default implementation uses object.ReferenceEquals() to compare.
        /// </summary>
        /// <param name="entity"></param>
        /// <returns></returns>
        public bool SameValueAs(IEntity entity)
        {
            return object.ReferenceEquals(this, entity);
        }

        #endregion         
    
        #region Equality Overrides

        public override bool Equals(object obj)
        {
            if (obj == null)
                return false;

            if (obj is EntityBase)
                return this.Equals((EntityBase)obj);
            else
                return false;
        }

        /// <summary>
        /// Compare whether two instances equals using this.SameEntityAs(). This can be 
        /// overriden.
        /// </summary>
        public virtual bool Equals(EntityBase obj)
        {
            if (obj == null)
                return false;

            return this.SameEntityAs(obj);
        }

        /// <summary>
        /// Default implementation returns the hash of the key.
        /// </summary>
        public override int GetHashCode()
        {
            return _key.GetHashCode();
        }

        public static bool operator ==(EntityBase a, EntityBase b)
        {
            if (System.Object.ReferenceEquals(a, b))
                return true;

            if (((object)a == null) || ((object)b == null))
                return false;

            return a.Equals(b);
        }

        public static bool operator !=(EntityBase a, EntityBase b)
        {
            return !(a == b);
        }

        #endregion
    }
}
